
/* 
 * This file is part of FreeBMS - an open source battery management system
 *
 * DTC Functions
 * 
 * NOTE: This code is designed to be compiled using SOURCEBOOST (http://www.sourceboost.com)
 */
 
/* This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
#include <system.h>
#include "dtc.h"
#include "eeprom.h"
#include "options.h"

// Define external functions
extern void eeprom_write(char address, char data);
extern char eeprom_read(char address);

struct dtc_data *first_dtc; 	// First DTC in list
bool error_memfault;			// Memory found inconsistent with polled data
bool error_trisfault;			// Tris registers have been overridden
bool error_sensorfault;			// No current sensor detected (or bogus values)
bool error_relayfault;			// Relays are not cutting off current flow
bool error_critical;			// Critical error has occurred
bool error_canerrors;			// Too many CANBUS errors have occurred

// Initialize DTC error booleans
void init_dtc() {
	error_memfault = false;
	error_trisfault = false;
	error_sensorfault = false;
	error_relayfault = false;
	error_canerrors = false;
	error_critical = false;
}

// Find a DTC record based on provided ID
struct dtc_data *find_dtc(BYTE id) {
	struct dtc_data *tmp_ptr;

	// Loop through DTCs to find ID
	for(tmp_ptr = first_dtc; tmp_ptr; tmp_ptr = tmp_ptr->next) {
		if(tmp_ptr->data[0] == id) {
			// Return the record pointer 
			return tmp_ptr;
		}
	}

	// Else return NULL, record not found
	return NULL;
}

// Update DTC entry
struct dtc_data *update_dtc(BYTE length, BYTE *buffer) {
	struct dtc_data *tmp_ptr;
	BYTE record = 0;
	bool priority_changed = false;

	// Disable interrupts
	do {
		clear_bit(intcon, GIE);
	} while(intcon.GIE == 1);

	// Find the DTC record
	tmp_ptr = find_dtc(buffer[0]);
	
	// Verify that record was successfully located then update values
	if(tmp_ptr != NULL) {
		// Set new length
		tmp_ptr->length = length;

		// If the priority changed (to high or medium) then flag it
		if(tmp_ptr->data[1] != buffer[1] && (buffer[1] == DTC_PRIORITY_HI || buffer[1] == DTC_PRIORITY_MED))
			priority_changed = true;

		// Loop from 1 to length and copy information to record buffer
		for(record = 0; record < length; ++record)
			tmp_ptr->data[record] = buffer[record];

		// Rewrite EEPROM if the priority of the DTC changed (it occurred again or more severely)
		if(priority_changed == true)
			save_dtcs();
	} else {
		// If we didn't find the record, insert a new one
		tmp_ptr = insert_dtc(length, buffer, true);	
	}

	// Enable interrupts
	do {
		set_bit(intcon, GIE);
	} while(intcon.GIE == 0);
	
	// Return reference to new pointer
	return tmp_ptr;
}

// Insert a DTC record into the linked list
struct dtc_data *insert_dtc(BYTE length, BYTE *buffer, BYTE save) {
	struct dtc_data *new_ptr;
	struct dtc_data *tmp_ptr;
	BYTE record = 0;

	// Create a new record
	new_ptr = (struct dtc_data *) alloc(sizeof(dtc_data));

	// Verify that record was created successfully
	if(new_ptr != NULL) {
		new_ptr->length = length;

		// Loop from 0 to length and copy information over
		for(record = 0; record < length; ++record)
			new_ptr->data[record] = buffer[record];

		// Zero out the next pointer
		new_ptr->next = NULL;

		// Link the new record into the list
		if(first_dtc == NULL || first_dtc == 0)
			first_dtc = new_ptr;
		else {
			for(tmp_ptr = first_dtc; tmp_ptr; tmp_ptr = tmp_ptr->next) {
				if(tmp_ptr->next == NULL) {
					tmp_ptr->next = new_ptr;
					break;
				}
			}
		}
 
		// Save DTCs in EEPROM if requested
		if(save == true)
			save_dtcs();
 
		// Return reference to new record
		return new_ptr;
	} else {
		// If we got here, we ran out of memory
		error_memfault = true;
		return NULL;
	}
}

// Update DTC storage in EEPROM
void save_dtcs() {
	struct dtc_data *tmp_ptr;
	BYTE count = 0, i = 0, position = 0, work = 0;

	// Read existing number of DTCs stored in EEPROM
	count = eeprom_read(EEPROM_DTC_NUM);
	
	// If count was not initialized, assume value of zero
	if(count == 0xFF)
		count = 0;
		
	// DTC queue is full, return
	if(count >= MAX_DTC_STORE)
		return;

	// Reset variable for use as a counter
	count = 0;

	// Disable interrupts
	do {
		clear_bit(intcon, GIE);
	} while(intcon.GIE == 1);
	
	// Loop through all DTCs and store as many as we can
	for(tmp_ptr = first_dtc; tmp_ptr && count < MAX_DTC_STORE; tmp_ptr = tmp_ptr->next) {
		// Clear watchdog
		clear_wdt();

		// Verify length is within limits
		if(tmp_ptr->length >= 8)
			continue;

		// Write length
		eeprom_write(EEPROM_DTC_START + position, tmp_ptr->length);
		delay_ms(5);
		position += 1;

		// Write data bytes
		for(i = 0; i < tmp_ptr->length; ++i) {
			// Clear watchdog timer
			clear_wdt();

			// Write value to EEPROM
			eeprom_write(EEPROM_DTC_START + position, tmp_ptr->data[i]);
			delay_ms(5);

			// Increment position
			position += 1;
		}
		
		// Increment counter
		count += 1;
	}

	// Update DTC counter in EEPROM
	eeprom_write(EEPROM_DTC_NUM, count);
	delay_ms(5);
	
	// Re-enable interrupts
	do {
		set_bit(intcon, GIE);
	} while(intcon.GIE == 0);
	
	return;
}

// Load DTCs from EEPROM
void load_dtcs(BYTE highonly) {
	BYTE count = 0;
	BYTE position = 0;
	BYTE length = 0;
	BYTE i = 0;
	BYTE j = 0;
	BYTE dtc_buf[8];
	
	// Load number of stored DTCs from EEPROM
	count = eeprom_read(EEPROM_DTC_NUM);

	// If count is 0xFF, it was not initialized
	if(count == 0xFF)
		return;

	// If count is larger than the maximum number of DTCs stored, trim it
	if(count > MAX_DTC_STORE)
		count = MAX_DTC_STORE;

	// Loop through 'count' times
	for(i = 0; i < count; ++i) {
		// Clear watchdog timer
		clear_wdt();
	
		// Read DTC length
		length = eeprom_read(EEPROM_DTC_START + position);
		position += 1;
		
		// If length is too long, stop loading
		if(length > 8)
			break;
	
		// Read DTC message bytes
		for(j = 0; j < length; ++j) {
			dtc_buf[j] = eeprom_read(EEPROM_DTC_START + position);
			position += 1;
		}

		// If we are only loading high priority DTCs, make sure this is high
		if(highonly == true && dtc_buf[1] != DTC_PRIORITY_HI)
			dtc_buf[1] = DTC_PRIORITY_LOW;

		// Insert DTC without saving to EEPROM
		insert_dtc(length, dtc_buf, false);
	}
}

// Delete all DTCs stored in EEPROM and reset
void clear_dtcs() {

	// Clear EEPROM DTCs by zeroing out stored number
	eeprom_write(EEPROM_DTC_NUM, 0x00);
	delay_ms(50);

	// Resetting the chip takes care of active DTCs
	asm
	{
		RESET;
	}
}